#include <vector>
#include <iostream>
#include <string>
#include <algorithm>

namespace impl
{
enum class action
{
    add,
    remove,
    clear,
    assign
};

auto to_string(action act) -> std::string
{
    switch (act)
    {
        case action::add:
            return "add";
        case action::remove:
            return "remove";
        case action::clear:
            return "clear";
        case action::assign:
            return "assign";
    }
    return {};
}

struct observer_notification
{
    action act{action::add};
    std::vector<size_t> affected_index{};
};
class collection_observer
{
  public:
    collection_observer() = default;
    virtual void notify_observer(observer_notification n) = 0;
    virtual ~collection_observer()
    {
    }
};

template <typename T, typename Allocator = std::allocator<T>>
class observable_vector final
{
  public:
    using size_type = typename std::vector<T, Allocator>::size_type;
    observable_vector() noexcept(noexcept(Allocator()))
        : observable_vector(Allocator())
    {
    }
    explicit observable_vector(const Allocator& alloc) noexcept : data_(alloc)
    {
    }
    observable_vector(size_type cnt,
                      const T& val,
                      const Allocator& alloc = Allocator())
        : data_(cnt, val, alloc)
    {
    }
    explicit observable_vector(size_type cnt,
                               const Allocator& alloc = Allocator())
        : data_(cnt, alloc)
    {
    }
    observable_vector(observable_vector&& other) noexcept
        : data_(std::move(other.data_))
    {
    }
    observable_vector(observable_vector&& other,
                      const Allocator& alloc = Allocator()) noexcept
        : data_(std::move(other.data_), alloc)
    {
    }
    observable_vector(const observable_vector& other) noexcept
        : data_(other.data_)
    {
    }
    observable_vector(std::initializer_list<T> initlist,
                      const Allocator& alloc = Allocator())
        : data_(initlist, alloc)
    {
    }
    template <typename Iter>
    observable_vector(Iter beg, Iter end, const Allocator& alloc = Allocator())
        : data_(beg, end, alloc)
    {
    }

    observable_vector& operator=(const observable_vector& other)
    {
        if (this != &other)
        {
            data_ = other.data_;
            notify_observer(action::assign);
        }
        return *this;
    }

    observable_vector& operator=(observable_vector&& other)
    {
        if (this != &other)
        {
            data_ = std::move(other.data_);
            notify_observer(action::assign);
        }
        return *this;
    }

    void push_back(T&& val)
    {
        data_.push_back(std::move(val));
        notify_observer(action::add);
    }

    void pop_back()
    {
        data_.pop_back();
        notify_observer(action::remove);
    }

    void clear()
    {
        data_.clear();
        notify_observer(action::clear);
    }

    size_type size() const noexcept
    {
        return data_.size();
    }

    [[nodiscard]] bool empty() const noexcept
    {
        return data_.empty();
    }

    void add_observer(collection_observer* o)
    {
        observers_.push_back(o);
    }

    void remove_observer(collection_observer* o)
    {
        observers_.erase(std::remove(observers_.begin(), observers_.end(), o),
                         observers_.end());
    }

  private:
    void notify_observer(action act)
    {
        for (auto o : observers_)
        {
            if (o)
            {
                observer_notification n{act};
                switch (act)
                {
                    case action::add:
                        n.affected_index.push_back(data_.size() - 1);
                        break;
                    case action::remove:
                        n.affected_index.push_back(data_.size() + 1);
                        break;
                }
                o->notify_observer(std::move(n));
            }
        }
    }

  private:
    std::vector<T, Allocator> data_;
    std::vector<collection_observer*> observers_;
};

class observer : public collection_observer
{
  public:
    void notify_observer(observer_notification n) override
    {
        std::cout << "action: " << to_string(n.act);
        if (!n.affected_index.empty())
        {
            std::cout << ", indexes: ";
            for (auto i : n.affected_index)
            {
                std::cout << i << ' ';
            }
        }
        std::cout << '\n';
    }
};

}  // namespace impl

using namespace std;
int main(int argc, char** argv)
{
    impl::observable_vector<int> v;
    impl::observer o;
    v.add_observer(&o);
    v.push_back(1);
    v.push_back(2);
    v.pop_back();
    v.clear();
    v.remove_observer(&o);
    v.push_back(1);
    v.push_back(2);
    v.add_observer(&o);
    impl::observable_vector<int> v2 = {3, 4, 5};
    v = v2;
    v = {7, 8, 9};
    return 0;
}